﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using HelixToolkit.Wpf;
using System.Runtime.Serialization;
using System.IO;
using System.Windows.Threading;
using System.ComponentModel;

namespace GameBoard
{
    [DataContract]
    public class VisualsViewModel : INotifyPropertyChanged
    {
        [DataMember]
        private TerrainViewModel _terrainViewModel = new TerrainViewModel();
        private VisualsViewModel()
        {
            Initialize();
        }
        static VisualsViewModel _instance;
        public static VisualsViewModel Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new VisualsViewModel();
                }
                return _instance;
            }
            private set
            {
                _instance = value;
            }
        }



        public enum TroopFormation
        {
            Grid, Circle, Random, Tight
        }

        public void SetInitialBackground()
        {
            var files = Directory.GetFiles(GameboardConstants.MapPictureDirectory);
            if (files.Any())
            {
                SetBoardBackground(files.First(), 0, 0, 0, true, null);
            }
            else
            {
                SetBoardToDefault();
            }
            //var tower = MakeModelVisual3DFromObjectFile(@"C:\GuardTower\GuardTower.obj", Materials.Gray, Materials.Brown);
            //var rotate = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1, 0, 0), 90), new Point3D(0, 0, 0));
            //tower.Transform = rotate;

            //_gameBoardVisual.AddVisual(tower);
            
        
        }




        private MapVisualFactory _theMapFactory;
        private TapeMeasurerControl _tapeMeasurerControl = new TapeMeasurerControl();

        [OnDeserialized]
        private void OnDeserialized(StreamingContext sc)
        {
            Initialize();
            _tapeMeasurerControl = new TapeMeasurerControl();
        }

        private void Initialize()
        {
            _temporaryVisuals = new Dictionary<DispatcherTimer, Guid>();
            _groupMovementCircles = new List<MovementCircleVisualFactory>();
            _gameBoardVisuals = new List<GameBoardVisual>();
        }

        Color defaultColor = Colors.Gray;
        public MoveablePicture AddImagePieceToMap(String charImageFile, Color pieceColor, String name, double height,
            Point3D location, List<StatusEffectDisplay> statusEffects, double speed, Guid characterGuid, List<int> movementCircleRadiusList)
        {
            double heightFeet = height;
            while (CharactersToMoveablePicture.Values.Any(
                existingImage =>
                    existingImage.Location.X == location.X &&
                    existingImage.Location.Y == location.Y &&
                    existingImage.Location.Z == location.Z))
            {
                location = new Point3D(location.X + heightFeet, location.Y, location.Z);
            }
            MoveablePicture charImage = new MoveablePicture(charImageFile,
                heightFeet,
                name, 
                pieceColor,
                location, 
                statusEffects, 
                speed,
                movementCircleRadiusList,
                characterGuid
                );
            RegisterMoveablePicture(charImage, characterGuid);
            return charImage;
        }

        private void RegisterMoveablePicture(MoveablePicture charImage, Guid characterGuid)
        {
            if(_charactersToMoveablePicture.ContainsKey(characterGuid))
            {
                _charactersToMoveablePicture.Remove(characterGuid);
            }
            _charactersToMoveablePicture.Add(characterGuid, charImage);
            foreach (var cur in _gameBoardVisuals)
            {
                cur.AddVisual(charImage.TheCharImageFactory.FactoryVisualID, charImage.TheCharImageFactory.MakeVisual());
                cur.AddVisual(charImage.TheBaseConeFactory.FactoryVisualID, charImage.TheBaseConeFactory.MakeVisual());
            }
        }

        Dictionary<DispatcherTimer, Guid> _temporaryVisuals =
            new Dictionary<DispatcherTimer, Guid>();
        public void DrawAttack(Guid attackerID, Guid targetID, Color materialColor, 
            Duration showDuration)
        {
            MoveablePicture attacker = _charactersToMoveablePicture[attackerID];
            MoveablePicture target = _charactersToMoveablePicture[targetID];

            
            ArrowVisualData data = new ArrowVisualData()
            {
                Origin = attacker.Location,
                Point2 = target.Location,
                MaterialColor = materialColor,
                Diameter = 1,
                ShowDuration = showDuration,
            };
            ArrowVisualFactory arrowFactory = new ArrowVisualFactory(data);
            foreach(var cur in _gameBoardVisuals)
            {
                cur.AddVisual(arrowFactory.FactoryVisualID, arrowFactory.MakeVisual());
            }
            MarkVisualAsTemporary(arrowFactory.FactoryVisualID, showDuration.TimeSpan);

        }

        private void MarkVisualAsTemporary(Guid guid, TimeSpan timespan)
        {
            DispatcherTimer timer = new DispatcherTimer();
            timer.Tick += removeTemporaryVisual;
            timer.Interval = timespan;
            timer.Start();
            _temporaryVisuals.Add(timer, guid);
        }

        private void removeTemporaryVisual(object sender, EventArgs e)
        {
            var timer = sender as DispatcherTimer;
            var matchingID = _temporaryVisuals[timer];
            foreach (var cur in _gameBoardVisuals)
            {
                cur.RemoveVisual(matchingID);
            }

            timer.Stop();
        }

        





        public event EventHandler PieceSelected;
        protected virtual void OnPieceSelected(PieceSelectedEventArgs e)
        {
            EventHandler handler = PieceSelected;
            if (handler != null)
            {
                handler(this, e);
            }
        }


        public event EventHandler ClearSelectedPieces;
        protected virtual void OnClearSelectedPieces(ClearSelectedPiecesEventArgs e)
        {
            EventHandler handler = ClearSelectedPieces;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public event EventHandler PieceMoved;
        protected virtual void OnPieceMoved(PieceMovedEventsArg e)
        {
            EventHandler handler = PieceMoved;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        public void ToggleSelectCharacterFromVisual(IVisual lastHit)
        {
            var characterID = 
                _charactersToMoveablePicture.First
                (x => x.Value.HasVisualWithID(lastHit.FactoryID)).Key;
            if (_charactersToMoveablePicture[characterID].IsSelected)
            {
                var previousSelections = CharactersToMoveablePicture.Where(x => x.Value.IsSelected).Select(z => z.Key).ToList();
                ClearSelectedCharacters();
                previousSelections.Remove(characterID);
                foreach (var cur in previousSelections)
                {
                    SetActive(cur);
                }
            }
            else
            {
                SetActive(characterID);
            }
        }

        private Guid IDFromPicture(MoveablePicture moveablePicture)
        {
            foreach (var cur in _charactersToMoveablePicture)
            {
                if (cur.Value == moveablePicture)
                {
                    return cur.Key;
                }
            }
            throw new Exception("Unregistered picture ID");
        }
        public bool MultiplesAreSelected
        {
            get
            {
                return _charactersToMoveablePicture.
                    Values.Count(x => x.IsSelected)>1;
            }
        }
        public void SetActive(Guid targetId)
        {
            MoveablePicture moveablePicture = _charactersToMoveablePicture[targetId];
            if (moveablePicture.IsSelected)
            {
                return;
            }
            moveablePicture.IsSelected = true;

            foreach (var cur in _gameBoardVisuals)
            {
                foreach(var curID in moveablePicture.ActivateableIDs)
                {
                    cur.ActivateVisuals(curID);
                }
            }
            foreach (var curBoard in _gameBoardVisuals)
            {
                if(moveablePicture.BoardsInvisibleTo.Contains(curBoard.BoardType))
                {
                    continue;
                }
                foreach (var cur in moveablePicture.MovementCirclesFactories)
                {
                    curBoard.AddVisual(cur.FactoryVisualID, cur.MakeVisual());
                }
                curBoard.AddVisual(moveablePicture.TheInfoTextFactory.FactoryVisualID, 
                    moveablePicture.TheInfoTextFactory.MakeVisual());
            }
            OnPieceSelected(new PieceSelectedEventArgs(targetId));
        }

        public void SetInactive(Guid targetID)
        {
            MoveablePicture moveablePicture = _charactersToMoveablePicture[targetID];
            ResetGroupMovementCircles();
            moveablePicture.IsSelected = false;
            foreach (var curBoard in _gameBoardVisuals)
            {
                foreach (var cur in moveablePicture.MovementCirclesFactories)
                {
                    curBoard.RemoveVisual(cur.FactoryVisualID);
                }
                curBoard.RemoveVisual(moveablePicture.TheInfoTextFactory.FactoryVisualID);
                curBoard.DeactivateVisuals(moveablePicture.TheBaseConeFactory.FactoryVisualID);
            }
        }

        public void RemovePiece(Guid uniqueID)
        {
            MoveablePicture match = _charactersToMoveablePicture[uniqueID];
            foreach (var curBoard in _gameBoardVisuals)
            {
                foreach (var cur in match.MovementCirclesFactories)
                {
                    curBoard.RemoveVisual(cur.FactoryVisualID);
                }
                curBoard.RemoveVisual(match.TheInfoTextFactory.FactoryVisualID);
                curBoard.RemoveVisual(match.TheBaseConeFactory.FactoryVisualID);
                curBoard.RemoveVisual(match.TheCharImageFactory.FactoryVisualID);
            }
            CharactersToMoveablePicture.Remove(uniqueID);
            match = null;
        }

        private BoardInfo _currentBoardInfo;
        [DataMember]
        public BoardInfo CurrentBoardInfo
        {
            get { return _currentBoardInfo; }
            set { _currentBoardInfo = value; }
        }
        

        internal void SetBoardBackground(string newImage,
            double definedBoardHeight, 
            double definedBoardWidth, 
            double hillHeight,
            bool maintainRatio, byte[] hillNoise)
        {
            CurrentBoardInfo = new BoardInfo(newImage,
                definedBoardHeight, 
                definedBoardWidth, 
                hillHeight,
                maintainRatio, 
                hillNoise);

            if (_theMapFactory != null)
            {
                RemoveVisuals(_theMapFactory.FactoryVisualID);
            }
            ImageBrush boardFrontBrush = new ImageBrush();
            Material frontMaterial;
            try
            {
                ImageBrush frontBrush = MaterialMaker.MakeImageMaterial(newImage);
                frontMaterial = new DiffuseMaterial(frontBrush);

                if (maintainRatio)
                {
                    if (definedBoardHeight <= 0)
                    {
                        CurrentBoardInfo.DefinedBoardHeight = frontBrush.ImageSource.Height / 4;
                        CurrentBoardInfo.DefinedBoardWidth = frontBrush.ImageSource.Width / 4;
                    }
                    else
                    {
                        CurrentBoardInfo.DefinedBoardHeight = definedBoardHeight;
                        double normalHeight = frontBrush.ImageSource.Height;
                        double normalWidth = frontBrush.ImageSource.Width;
                        double ratio = normalHeight / normalWidth;
                        CurrentBoardInfo.DefinedBoardWidth = (definedBoardHeight / ratio);
                    }
                }
                else
                {
                    CurrentBoardInfo.DefinedBoardHeight = definedBoardHeight;
                    CurrentBoardInfo.DefinedBoardWidth = definedBoardWidth;
                }
            }
            catch (Exception)
            {
                MessageBox.Show("Unable to set map background to " + newImage);
                SetBoardToDefault();
                return;
            }
            Material backMaterial = MaterialMaker.PaperbackMaterial();

            MapVisualData data = new MapVisualData(CurrentBoardInfo, frontMaterial, backMaterial);
            _theMapFactory = new MapVisualFactory(data);
            foreach (var curboard in _gameBoardVisuals)
            {
                curboard.AddVisual(_theMapFactory.FactoryVisualID, _theMapFactory.MakeVisual());
            }
        }

        private void SetBoardToDefault()
        {
            Material frontMaterial = MaterialMaker.DefaultFrontMaterial;
            Material backMaterial = MaterialMaker.PaperbackMaterial();
            CurrentBoardInfo = new BoardInfo("", 300, 300, 0, true, null);
            MapVisualData data = new MapVisualData(CurrentBoardInfo, frontMaterial, backMaterial);
            _theMapFactory = new MapVisualFactory(data);
            foreach (var curboard in _gameBoardVisuals)
            {
                curboard.AddVisual(_theMapFactory.FactoryVisualID, _theMapFactory.MakeVisual());
            }
        }

        public void MovePieceToPoint(Guid selectedID, Point3D targetPoint)
        {
            var matchingVisual = _charactersToMoveablePicture[selectedID];
            var dest = new Point3D(targetPoint.X, targetPoint.Y, targetPoint.Z);
            dest.Z += matchingVisual.FlightHeight;
            matchingVisual.SetLocationTo(dest);
            foreach (var curboardVisual in _gameBoardVisuals)
            {
                curboardVisual.MoveVisual(matchingVisual.TheBaseConeFactory.FactoryVisualID, dest);
                curboardVisual.MoveVisual(matchingVisual.TheCharImageFactory.FactoryVisualID, dest);
                curboardVisual.MoveVisual(matchingVisual.TheInfoTextFactory.FactoryVisualID, dest);
            }

            OnPieceMoved(new PieceMovedEventsArg(selectedID, targetPoint));
        }

        internal void MoveSelectedPiecesTo(Point3D point3D)
        {
            var selectedCharacters = CharactersToMoveablePicture.Where(x => x.Value.IsSelected);
            if (selectedCharacters.Count() == 1)
            {
                var fc = selectedCharacters.First().Value;
                Point3D targetPoint = new Point3D(point3D.X, point3D.Y, point3D.Z);
                MovePieceToPoint(fc.UniqueID, targetPoint);
                OnPieceMoved(new PieceMovedEventsArg(IDFromPicture(fc), targetPoint));
            }
            else
            {
                //Move in formation to new location with respect to common centerpoint.
                var middleSelection = Helper3DCalcs.FindMidpoint(selectedCharacters.Select(x => x.Value.Location));
                foreach (var cur in selectedCharacters)
                {
                    var curP = cur.Value.Location;
                    var vectorToOldMidpoint = curP - middleSelection;
                    vectorToOldMidpoint.Normalize();
                    if (double.IsNaN(vectorToOldMidpoint.X) 
                        || 
                        double.IsNaN(vectorToOldMidpoint.Y) 
                        || double.IsNaN(vectorToOldMidpoint.Z))
                    {
                        MovePieceToPoint(cur.Value.UniqueID, new Point3D(point3D.X, point3D.Y, point3D.Z));
                        continue;
                    }
                    double distance = Helper3DCalcs.DistanceBetween(curP, middleSelection);
                    vectorToOldMidpoint = vectorToOldMidpoint * distance;
                    var np = new Point3D(vectorToOldMidpoint.X + point3D.X, vectorToOldMidpoint.Y + point3D.Y, vectorToOldMidpoint.Z + point3D.Z);
                    Point3D targetPoint = new Point3D(np.X, np.Y, np.Z);
                    MovePieceToPoint(cur.Value.UniqueID, targetPoint);
                    OnPieceMoved(new PieceMovedEventsArg(IDFromPicture(cur.Value), targetPoint));
                }
            }

        }

        internal void ClearSelectedCharacters()
        {
            foreach (var cur in CharactersToMoveablePicture.Where(x => x.Value.IsSelected))
            {
                SetInactive(cur.Key);
            }
            OnClearSelectedPieces(new ClearSelectedPiecesEventArgs());
        }

        private List<MovementCircleVisualFactory> _groupMovementCircles = new List<MovementCircleVisualFactory>();
        public List<MovementCircleVisualFactory> GroupMovementCircles
        {
            get { return _groupMovementCircles; }
            set { _groupMovementCircles = value; }
        }

        public void DrawGroupMovementCircleIfNeeded()
        {
            ResetGroupMovementCircles();
            List<Point3D> selectedPictures = CharactersToMoveablePicture.Where
                (x => x.Value.IsSelected).Select(z => z.Value.Location).ToList();
            if (selectedPictures.Count>1)
            {
                double minSpeed = CharactersToMoveablePicture.Min(x => x.Value.Speed);
                Point3D midPoint = Helper3DCalcs.FindMidpoint(selectedPictures);
                var movementCircleRadiusList = CharactersToMoveablePicture.FirstOrDefault(x => x.Value.IsSelected).Value.MovementCirclesRadiusList;
                foreach (var curCircleSize in movementCircleRadiusList)
                {
                    MovementCircleVisualFactory g = new MovementCircleVisualFactory(
                        new MovementCircleVisualData()
                        {
                            Speed = minSpeed,
                            CurCircleSize = curCircleSize,
                            Location = midPoint,
                            MaterialColor = Colors.DarkBlue
                        });
                    GroupMovementCircles.Add(g);
                    foreach(var cur in _gameBoardVisuals)
                    {
                        cur.AddVisual(g.FactoryVisualID, g.MakeVisual());
                    }
                }
                foreach (var cur in CharactersToMoveablePicture.Where(x => x.Value.IsSelected))
                {
                    foreach (var curMovementCircle in cur.Value.MovementCirclesFactories)
                    {
                        foreach (var curboard in _gameBoardVisuals)
                        {
                            curboard.RemoveVisual(curMovementCircle.FactoryVisualID);
                        }
                    }
                }
            }
        }

        private void ResetGroupMovementCircles()
        {
            foreach (var cur in GroupMovementCircles)
            {
                foreach (var curboard in _gameBoardVisuals)
                {
                    curboard.RemoveVisual(cur.FactoryVisualID);
                }
            }
            GroupMovementCircles.Clear();
        }



        private void SelectFromInsideShape(Point3D point1, Point3D point2)
        {
            List<MoveablePicture> selectedByShape = new List<MoveablePicture>();
            ClearSelectedCharacters();
            foreach (var cur in CharactersToMoveablePicture)
            {
                switch (_terrainViewModel.ShapePlacementType)
                {
                    case TerrainViewModel.ShapeMode.Sphere:
                        double dist = Helper3DCalcs.DistanceBetween(cur.Value.Location, point1);
                        if (dist <= _terrainViewModel.ShapeSizeValue)
                        {
                            selectedByShape.Add(cur.Value);
                        }
                        break;
                    case TerrainViewModel.ShapeMode.Cone:
                        //public static Boolean IsPointInCone(Point3D testPoint, Point3D coneApex, Point3D coneBaseCenter,
                        //            double aperture)
                        //
                        //     aperture angle
                        //       | \
                        //    H  |  \
                        //       |___\
                        //         R
                        var coneBaseCenter = Helper3DCalcs.MovePointTowards(point1, point2,
                            _terrainViewModel.ShapeSizeValue);
                        var coneHeight = Helper3DCalcs.DistanceBetween(point1, coneBaseCenter);
                        var angle = Math.Atan(1);
                        if (Helper3DCalcs.IsPointInCone(cur.Value.Location, point1, coneBaseCenter, angle * 2))
                        {
                            selectedByShape.Add(cur.Value);
                        }
                        break;
                    case TerrainViewModel.ShapeMode.Line:
                        Vector3D testpt = cur.Value.Location.ToVector3D();
                        Vector3D pt1 = point1.ToVector3D();
                        Vector3D pt2 = point2.ToVector3D();
                        double lengthsq = Math.Pow(Helper3DCalcs.DistanceBetween(point1, point2), 2);
                        if (Helper3DCalcs.PointIsInsideCylinder(testpt, pt1, pt2, LineDiameter, lengthsq))
                        {
                            selectedByShape.Add(cur.Value);
                        }
                        break;

                    case TerrainViewModel.ShapeMode.Square:
                        break;
                }

            }
            foreach (var cur in selectedByShape)
            {
                //public void SetActive(MoveablePicture moveablePicture, Color pieceColor, bool drawSingleSelectionDetails, double curSpeed, List<StatusEffectDisplay> statuses)
                SetActive(IDFromPicture(cur));
            }

            DrawGroupMovementCircleIfNeeded();
        }



        internal void DrawSphere(Point3D point3D)
        {
            var shapeData = new SphereVisualData()
            {
                Center = point3D,
                Radius = _terrainViewModel.ShapeSizeValue,
                MaterialFile = _terrainViewModel.ObjectPlacementImage,
                MaterialColor = SelectedTeamColor(),
                IsNotSolid = _terrainViewModel.ShapeIsTransparent,
                IsTransparent = _terrainViewModel.ShapeIsTransparent
            };
            SphereVisualFactory sp = new SphereVisualFactory(shapeData);
            foreach (var cur in _gameBoardVisuals)
            {
                cur.AddVisual(sp.FactoryVisualID, sp.MakeVisual());
            }
            if (!_terrainViewModel.ShapeIsPermanent)
            {
                MarkVisualAsTemporary(sp.FactoryVisualID, TimeSpan.FromSeconds(3));
                SelectFromInsideShape(point3D, point3D);
            }
            else
            {
                _terrainViewModel.RegisterFactory(sp);
            }
        }

        internal void DrawCone(Point3D point1, Point3D point2)
        {
            Vector3D dir = point2 - point1;
            dir.Normalize();
            var shape = new ConeVisualData
            {
                MaterialFile = _terrainViewModel.ObjectPlacementImage,
                MaterialColor = SelectedTeamColor(),
                BaseRadius = 2,
                Height = _terrainViewModel.ShapeSizeValue,
                TopRadius = _terrainViewModel.ShapeSizeValue,
                Origin = point1,
                Normal = dir,
                IsNotSolid = _terrainViewModel.ShapeIsTransparent,
                IsTransparent = _terrainViewModel.ShapeIsTransparent
            };
            ConeVisualFactory cvf = new ConeVisualFactory(shape);
            foreach (var cur in _gameBoardVisuals)
            {
                cur.AddVisual(cvf.FactoryVisualID, cvf.MakeVisual());
            }
            if (!_terrainViewModel.ShapeIsPermanent)
            {
                MarkVisualAsTemporary(cvf.FactoryVisualID, TimeSpan.FromSeconds(3));
                SelectFromInsideShape(point1, point2);
            }
            else
            {
                _terrainViewModel.RegisterFactory(cvf);
            }
        }

        internal void DrawCube(Point3D point1, Point3D point3D)
        {
            var angle = Vector3D.AngleBetween(new Vector3D(0, 1, 0), point3D - point1);
            var shape = new CubeVisualData()
            {
                MaterialFile = _terrainViewModel.ObjectPlacementImage,
                MaterialColor = SelectedTeamColor(),
                Height = _terrainViewModel.ShapeHeightValue,
                Width = _terrainViewModel.ShapeWidthValue,
                Length = _terrainViewModel.ShapeSizeValue,
                Angle = angle,
                Center = new Point3D(point1.X, point1.Y, point1.Z + (_terrainViewModel.ShapeHeightValue / 2)),
                IsNotSolid = _terrainViewModel.ShapeIsTransparent,
                IsTransparent = _terrainViewModel.ShapeIsTransparent
            };
            CubeVisualFactory cvf = new CubeVisualFactory(shape);
            foreach (var cur in _gameBoardVisuals)
            {
                cur.AddVisual(cvf.FactoryVisualID, cvf.MakeVisual());
            }
            if (!_terrainViewModel.ShapeIsPermanent)
            {
                MarkVisualAsTemporary(cvf.FactoryVisualID, TimeSpan.FromSeconds(3));
                SelectFromInsideShape(point3D, point3D);
            }
            else
            {
                _terrainViewModel.RegisterFactory(cvf);
            }
        }

        public void DrawGrid(int squareSizes)
        {

            var data = new GridVisualData()
            {
                Length = CurrentBoardInfo.DefinedBoardWidth,
                Width = CurrentBoardInfo.DefinedBoardHeight,
                Thickness = .3,
                MajorDistance = squareSizes,
                MinorDistance = squareSizes,
                Center = new Point3D(0, 0, 1),
                MaterialColor = Colors.Black,
            };
            if (_visualGridFactory == null)
            {
                _visualGridFactory = new GridVisualFactory(data);
            }
            foreach (var cur in _gameBoardVisuals)
            {
                cur.AddVisual(_visualGridFactory.FactoryVisualID, _visualGridFactory.MakeVisual());
            }
        }



        const int LineDiameter = 5;
        internal void DrawLine(Point3D point1, Point3D point2)
        {
            var secondPoint = Helper3DCalcs.MovePointTowards(point1, point2, 
                _terrainViewModel.ShapeSizeValue);
            var pathinfo = new Point3DCollection(new List<Point3D>() { point1, secondPoint });
            var shape = new TubeVisualData()
            {
                MaterialFile = _terrainViewModel.ObjectPlacementImage,
                MaterialColor = SelectedTeamColor(),
                Path = pathinfo,
                Diameter = LineDiameter,
                IsNotSolid = _terrainViewModel.ShapeIsTransparent,
                IsTransparent = _terrainViewModel.ShapeIsTransparent,
            };
            TubeVisualFactory cvf = new TubeVisualFactory(shape);
            foreach (var cur in _gameBoardVisuals)
            {
                cur.AddVisual(cvf.FactoryVisualID, cvf.MakeVisual());
            }
            if (!_terrainViewModel.ShapeIsPermanent)
            {
                MarkVisualAsTemporary(cvf.FactoryVisualID, TimeSpan.FromSeconds(3));
                SelectFromInsideShape(point1, point2);
            }
            else
            {
                _terrainViewModel.RegisterFactory(cvf);
            }
        }

        internal void DrawObjectFile(Point3D point3D)
        {
            var data = ReadFromXML(_terrainViewModel.CurrentObjectFile,
                typeof(ObjectVisualData)) as ObjectVisualData;
            data.ObjectCenter = point3D;
            ObjectVisualFactory objfact = new ObjectVisualFactory(data);
            foreach (var cur in _gameBoardVisuals)
            {
                cur.AddVisual(objfact.FactoryVisualID, objfact.MakeVisual());
            }
            _terrainViewModel.RegisterFactory(objfact);
        }
        private Object ReadFromXML(string p0, Type theType)
        {
            using (var reader = new FileStream(p0, FileMode.Open, FileAccess.Read))
            {
                var ser = new DataContractSerializer(theType);
                return ser.ReadObject(reader);
            }
        }


        public Color SelectedTeamColor()
        {
            var selectedCharacter = CharactersToMoveablePicture.FirstOrDefault(cur => cur.Value.IsSelected);
            if (selectedCharacter.Value != null)
                return selectedCharacter.Value.PieceColor;
            return Colors.Red;
        }

        private void ZoomTo(Point3D targetPoint, BoardsViewModel.BoardTypes targetTypes,
            double zoomHeight = 0)
        {
            if (zoomHeight == 0)
            {
                zoomHeight = 25;
            }
            var zoomSize = new Size3D(0, 0, zoomHeight);

            foreach (var cur in _gameBoardVisuals)
            {
                if (targetTypes == BoardsViewModel.BoardTypes.All 
                    || targetTypes == cur.BoardType)
                {
                    cur.ZoomTo(new Rect3D(targetPoint, zoomSize), 500);
                }
            }
        }


        GridVisualFactory _visualGridFactory = null;
        public GridVisualFactory VisualGrid
        {
            get
            {
                return _visualGridFactory;
            }
            set
            {
                _visualGridFactory = value;
            }
        }



        public void RemoveGrid()
        {
            if (_visualGridFactory != null)
            {
                foreach (var cur in _gameBoardVisuals)
                {
                    cur.RemoveVisual(_visualGridFactory.FactoryVisualID);
                }
            }
        }


        private bool _tapeMeasurerActive = false;
        public bool TapeMeasurerActive
        {
            get { return _tapeMeasurerActive; }
            set
            {
                _tapeMeasurerActive = value;
                if (!value)
                {
                    _tapeMeasurerControl.CleanupTapeMeasurerVisuals();
                }
            }
        }



        internal void HandleTapeMeasurerHit(Point3D hit)
        {
            _tapeMeasurerControl.HandleHit(hit);
            if (_tapeMeasurerControl.TapeMeasurerDrawMode == TapeMeasurerControl.DrawModeTapeMeasurer.StartOnly)
            {
                foreach (var cur in _gameBoardVisuals)
                {
                    cur.AddVisual(_tapeMeasurerControl.StartPointFactory.FactoryVisualID, 
                        _tapeMeasurerControl.StartPointFactory.MakeVisual());
                }
            }
            else if (_tapeMeasurerControl.TapeMeasurerDrawMode == TapeMeasurerControl.DrawModeTapeMeasurer.End)
            {
                foreach (var cur in _gameBoardVisuals)
                {
                    cur.AddVisual(_tapeMeasurerControl.EndPointFactory.FactoryVisualID,
                        _tapeMeasurerControl.EndPointFactory.MakeVisual());
                    cur.AddVisual(_tapeMeasurerControl.TextVisualFactory.FactoryVisualID,
                        _tapeMeasurerControl.TextVisualFactory.MakeVisual());
                    cur.AddVisual(_tapeMeasurerControl.DistanceIndicatorFactory.FactoryVisualID,
                        _tapeMeasurerControl.DistanceIndicatorFactory.MakeVisual());

                }
            }
        }


        public void OpenVisuals(Dictionary<Guid, MoveablePicture> readCharacters)
        {
            foreach (var curPiece in readCharacters)
            {
                RegisterMoveablePicture(curPiece.Value, curPiece.Key);
            }
        }

        public void OpenBoardInfo(BoardInfo savedinfo)
        {
            SetBoardBackground(savedinfo.BoardImageName, 
                savedinfo.DefinedBoardHeight, 
                savedinfo.DefinedBoardWidth, 
                savedinfo.BoardHillValue,
                savedinfo.MaintainRatio,
                savedinfo.NoisePermutations);
        }

        private List<GameBoardVisual> _gameBoardVisuals = new List<GameBoardVisual>();
        public void RegisterGameBoardVisual(GameBoardVisual gameBoardVisual)
        {
            _gameBoardVisuals.Add(gameBoardVisual);
            if (gameBoardVisual.BoardType == BoardsViewModel.BoardTypes.Main)
            {
                MainCamera = gameBoardVisual.Camera;
            }
            PopulatedGameBoardWithVisuals(gameBoardVisual);
        }

        private void PopulatedGameBoardWithVisuals(GameBoardVisual gameBoardVisual)
        {
            foreach (var curPiece in _charactersToMoveablePicture.Values)
            {
                if(curPiece.BoardsInvisibleTo.Contains(gameBoardVisual.BoardType))
                {
                    continue;
                }
                gameBoardVisual.AddVisual(curPiece.TheCharImageFactory.FactoryVisualID, curPiece.TheCharImageFactory.MakeVisual());
                gameBoardVisual.AddVisual(curPiece.TheBaseConeFactory.FactoryVisualID, curPiece.TheBaseConeFactory.MakeVisual());
            }
            if (_theMapFactory != null)
            {
                gameBoardVisual.AddVisual(_theMapFactory.FactoryVisualID, _theMapFactory.MakeVisual());
            }
            _terrainViewModel.PopulateBoardWithTerrain(gameBoardVisual);
            
        }

        [DataMember]
        private Dictionary<Guid, MoveablePicture> _charactersToMoveablePicture = new Dictionary<Guid, MoveablePicture>();
        public Dictionary<Guid, MoveablePicture> CharactersToMoveablePicture
        {
            get { return _charactersToMoveablePicture; }
            set { _charactersToMoveablePicture = value; }
        }

        internal bool HasAssociatedVisual(Guid characterGuid)
        {
            return _charactersToMoveablePicture.ContainsKey(characterGuid);
        }

        public MoveablePicture MatchingVisual(Guid characterGuid)
        {
            return _charactersToMoveablePicture[characterGuid];
        }

        public void RemakeInfoTextFor(Guid guid, List<StatusEffectDisplay> statuses)
        {
            _charactersToMoveablePicture[guid].RemakeInfoText(statuses);
        }

        public void ZoomTo(List<Guid> visualIDs, BoardsViewModel.BoardTypes targetTypes)
        {
            foreach (var cur in _gameBoardVisuals)
            {
                if (targetTypes != BoardsViewModel.BoardTypes.All &&
                    cur.BoardType != targetTypes)
                {
                    continue;
                }
                List<MoveablePicture> matchingPictures = new List<MoveablePicture>();
                foreach (var curID in visualIDs)
                {
                    var matchingChar = _charactersToMoveablePicture[curID];
                    if(!matchingChar.BoardsInvisibleTo.Contains(cur.BoardType))
                    {
                        matchingPictures.Add(matchingChar);
                    }
                }
                if (matchingPictures.Any())
                {
                    var midpoint = Helper3DCalcs.FindMidpoint(matchingPictures.Select(x => x.Location));
                    ZoomTo(midpoint, targetTypes);
                }
            }
        }


        public void ZoomTo(Guid guid, BoardsViewModel.BoardTypes targetTypes)
        {
            ZoomTo(new List<Guid>() { guid }, targetTypes);
        }
        internal void ZoomToPlacedTerrainVisual(Guid guid, BoardsViewModel.BoardTypes targetTypes)
        {
            var matchingData = _terrainViewModel.GetTerrainDataFor(guid);
            ZoomTo(matchingData.CenterOfTerrainFeature, targetTypes, matchingData.ZoomHeight);
        }

        internal void OtherBoardPieceMoved(object sender, EventArgs e)
        {
            var movedEvent = e as PieceMovedEventsArg;
            var matchingVisual = _charactersToMoveablePicture[movedEvent.MoverID];
            matchingVisual.SetLocationTo(new Point3D(movedEvent.Destination.X, movedEvent.Destination.Y, movedEvent.Destination.Z));
        }



        public void ResetVisualDataFor(Guid UniqueCharacterID, string name, int speed, int height)
        {
            var matchingCharacter = CharactersToMoveablePicture[UniqueCharacterID];
            if (matchingCharacter != null)
            {
                MoveablePicture replacement = new MoveablePicture(matchingCharacter.PictureFileName, height, name,
                    matchingCharacter.PieceColor, 
                    matchingCharacter.Location, 
                    matchingCharacter.StatusEffects, 
                    speed,
                    matchingCharacter.MovementCirclesRadiusList,
                    UniqueCharacterID);
                RemovePiece(UniqueCharacterID);
                RegisterMoveablePicture(replacement, UniqueCharacterID);

            }

        }

        public void MoveSelectedPiecesToFormation(TroopFormation selectedFormation)
        {
            var selectedCharacters = CharactersToMoveablePicture.Where(x => x.Value.IsSelected).Select(z=>z.Value).ToList();
            if (selectedCharacters.Count <= 1)
            {
                return;
            }
            var averagePoint = Helper3DCalcs.FindMidpoint(selectedCharacters.Select(x => x.Location));
            switch (selectedFormation)
            {
                case TroopFormation.Circle:
                    MoveCharactersToCircleFormation(
                        _terrainViewModel.ShapeSizeValue, selectedCharacters, averagePoint);
                    break;
                case TroopFormation.Grid:
                    MoveCharactersToGridFormation(_terrainViewModel.ShapeSizeValue, selectedCharacters, averagePoint);
                    break;
                case TroopFormation.Random:
                    MoveCharactersToRandomFormation(_terrainViewModel.ShapeSizeValue, selectedCharacters, averagePoint);
                    break;
                case TroopFormation.Tight:
                    MoveCharactersToGridFormation(selectedCharacters.Max(x=>x.LongestPictureSide), selectedCharacters, averagePoint);
                    break;

            }


        }

        private void MoveCharactersToGridFormation(double distance, List<MoveablePicture> selectedCharacters, Point3D averagePoint)
        {
            double squareSize = Math.Ceiling(Math.Sqrt(selectedCharacters.Count));
            //We move the average up and to the left so we can lazily lay out the characters in the grid.
            var adjust = (squareSize/2)*distance;
            var movedAverage = new Point3D(averagePoint.X - adjust, averagePoint.Y - adjust, averagePoint.Z);
            int curRow = 0;
            int curColumn = 0;
            foreach (var curCharacter in selectedCharacters)
            {
                var newLocation = new Point3D(movedAverage.X + (curColumn * distance), movedAverage.Y + (curRow * distance), curCharacter.Location.Z);
                MovePieceToPoint(curCharacter.UniqueID, newLocation);
                curColumn++;
                if (curColumn == squareSize)
                {
                    curRow++;
                    curColumn = 0;
                }

            }
        }

        private static Random _random = new Random();
        private void MoveCharactersToRandomFormation(double distance, List<MoveablePicture> selectedCharacters, Point3D averagePoint)
        {
            var minDistanceFromOther = selectedCharacters.Max(x => x.LongestPictureSide);
            int maxAttemptsAtNoOverlap = 3;

            foreach (var curCharacter in selectedCharacters)
            {
                int curAttempstAtNoOverlap = 0;
                Point3D newPosition = new Point3D();
                while (curAttempstAtNoOverlap < maxAttemptsAtNoOverlap)
                {
                    var curDirection = Helper3DCalcs.RandomVectorXY();
                    var randomdistance = _random.Next(1, (int)distance + 1);
                    newPosition = averagePoint + (curDirection * randomdistance);
                    bool anyTooClose = false;
                    foreach (var curOther in selectedCharacters)
                    {
                        if (curCharacter == curOther)
                        {
                            continue;
                        }
                        if ((curOther.Location - newPosition).Length < minDistanceFromOther)
                        {
                            anyTooClose = true;
                            break;
                        }
                    }
                    if (!anyTooClose)
                    {
                        break;
                    }
                    curAttempstAtNoOverlap++;
                }
                MovePieceToPoint(curCharacter.UniqueID, newPosition);
            }

        }

        private void MoveCharactersToCircleFormation(double distance, List<MoveablePicture> selectedCharacters, Point3D averagePoint)
        {
            double pointsOnCircle = selectedCharacters.Count;
            double circleSections = 360 / pointsOnCircle;
            var newLocations = Helper3DCalcs.CirclePoints(distance, averagePoint, circleSections);
            for (int curIndex = 0; curIndex < selectedCharacters.Count; curIndex++)
            {
                var curchar = selectedCharacters[curIndex];
                var newPosition = newLocations[curIndex];
                MovePieceToPoint(curchar.UniqueID, newPosition);
            }
        }



        public int NumberActive
        {
            get
            {
                return _charactersToMoveablePicture.Count(x => x.Value.IsSelected);
            }
        }

        MapExtensionVisualFactory _extraMapSpace = null;
        public void ShowMapExpansion(bool showExpansion)
        {
            if (showExpansion)
            {
                var data = new MapExentionVisualData()
                {
                    Origin = new Point3D(0, 0, -2),
                    Length = CurrentBoardInfo.DefinedBoardWidth * 100,
                    MaterialColor = Colors.Gray,
                    Width = CurrentBoardInfo.DefinedBoardHeight * 100,
                };
                _extraMapSpace = new MapExtensionVisualFactory(data);
                foreach (var cur in _gameBoardVisuals)
                {
                    cur.AddVisual(_extraMapSpace.FactoryVisualID, _extraMapSpace.MakeVisual());
                }
            }
            else
            {
                foreach (var cur in _gameBoardVisuals)
                {
                    cur.RemoveVisual(_extraMapSpace.FactoryVisualID);
                }
                _extraMapSpace = null;
            }

        }



        internal void ToggleCharacterInvisible(List<Guid> targetCharacters, 
            BoardsViewModel.BoardTypes theType)
        {

            foreach (var curID in targetCharacters)
            {
                var curChar = _charactersToMoveablePicture[curID];
                if (!curChar.BoardsInvisibleTo.Contains(theType))
                {
                    MakeInvisible(curChar, theType);
                }
                else
                {
                    MakeNotInvisible(curChar, theType);
                }
            }

        }

        private void MakeNotInvisible(MoveablePicture curChar, BoardsViewModel.BoardTypes theType)
        {
            foreach (var curBoard in _gameBoardVisuals.Where(x => x.BoardType == theType))
            {
                curBoard.AddVisual(curChar.TheCharImageFactory.FactoryVisualID,
                            curChar.TheCharImageFactory.MakeVisual());
                curBoard.AddVisual(curChar.TheBaseConeFactory.FactoryVisualID,
                    curChar.TheBaseConeFactory.MakeVisual());
            }
            curChar.MarkNotInvisibleOn(theType);
        }

        private void MakeInvisible(MoveablePicture curChar, BoardsViewModel.BoardTypes theType)
        {
            foreach (var curBoard in _gameBoardVisuals.Where(x => x.BoardType == theType))
            {
                foreach (var cur in curChar.MovementCirclesFactories)
                {
                    curBoard.RemoveVisual(cur.FactoryVisualID);
                }
                curBoard.RemoveVisual(curChar.TheBaseConeFactory.FactoryVisualID);
                curBoard.RemoveVisual(curChar.TheCharImageFactory.FactoryVisualID);
                curBoard.RemoveVisual(curChar.TheInfoTextFactory.FactoryVisualID);
            }
            curChar.MarkInvisibleOn(theType);
        }




        
        internal void RemoveVisuals(Guid guid)
        {
            foreach (var cur in _gameBoardVisuals)
            {
                cur.RemoveVisual(guid);
            }
        }


        public bool LockRotation 
        {
            set
            {
                foreach (var cur in _gameBoardVisuals)
                {
                    cur.LockRotation = value;
                }
            }
        }



        ProjectionCamera _mainCamera;
        public ProjectionCamera MainCamera
        {
            get
            {
                return _mainCamera;
            }
            set
            {
                if (_mainCamera != null)
                {
                    _mainCamera.Changed -= MainCameraViewChanged;
                }
                _mainCamera = value;
                if (_mainCamera != null)
                {
                    _mainCamera.Changed += MainCameraViewChanged;
                }
            }
        }
        private void MainCameraViewChanged(object sender, EventArgs e)
        {
            if (!OrientAllCamerasToMatchMain)
            {
                return;
            }
            VisualsViewModel.Instance.SetCamerasToMatchMain();
        }


        public void SetCamerasToMatchMain()
        {
            var mainBoard = _gameBoardVisuals.FirstOrDefault(x => x.BoardType == BoardsViewModel.BoardTypes.Main);
            foreach (var cur in _gameBoardVisuals.Where(x => x.BoardType != BoardsViewModel.BoardTypes.Main))
            {
                cur.MatchOtherCamera(mainBoard.Camera);
            }

        }
        private bool _orientAllCamerasToMatchMain = true;
        public bool OrientAllCamerasToMatchMain
        {
            get { return _orientAllCamerasToMatchMain; }
            set
            {
                _orientAllCamerasToMatchMain = value;
            }
        }

        internal void ClearViewModel()
        {
            foreach (var curKey in _charactersToMoveablePicture.Keys.ToList())
            {
                RemovePiece(curKey);
            }
            _gameBoardVisuals.Clear();
            _terrainViewModel.ClearViewModel();
            MainCamera = null;

        }


        
		internal void RemovePlacedTerrainVisual(BaseTerrainData terrainData)
        {
            RemoveVisuals(terrainData.FactoryID);
            _terrainViewModel.RemoveTerrainVisualData(terrainData.FactoryID);
            
        }

        public TerrainViewModel TerrainViewModel
        {
            get
            {
                return _terrainViewModel;
            }
        }

        internal void SetMouseCursorToAoEFor(TerrainViewModel.ShapeMode value)
        {
            foreach (var cur in _gameBoardVisuals)
            {
                cur.SetMouseCursorToAoEFor(value);
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion

        internal void OpenTerrain(TerrainViewModel terrainViewModel)
        {
            _terrainViewModel.PopulateFromViewModel(terrainViewModel); ;
        }

        public void ChangeElevationBy(Guid guid, double heightChangeValue)
        {
            var pointHack = new Point3D(_charactersToMoveablePicture[guid].Location.X,
                _charactersToMoveablePicture[guid].Location.Y,
                _charactersToMoveablePicture[guid].Location.Z - 
                _charactersToMoveablePicture[guid].FlightHeight);
            _charactersToMoveablePicture[guid].FlightHeight += heightChangeValue;
            MovePieceToPoint(guid, pointHack);
        }

        public void SetElevationTo(Guid guid, double targetHeightValue)
        {
            ChangeElevationBy(guid, targetHeightValue - _charactersToMoveablePicture[guid].FlightHeight);
        }



        public void SetHillsToLevel(double newHillValue)
        {
            SetBoardBackground(
                CurrentBoardInfo.BoardImageName, 
                CurrentBoardInfo.DefinedBoardHeight, 
                CurrentBoardInfo.DefinedBoardWidth, 
                newHillValue, 
                CurrentBoardInfo.MaintainRatio,
                CurrentBoardInfo.NoisePermutations);
        }

        public void ChangeBoardSeed()
        {
            SetBoardBackground(
                CurrentBoardInfo.BoardImageName,
                CurrentBoardInfo.DefinedBoardHeight,
                CurrentBoardInfo.DefinedBoardWidth,
                CurrentBoardInfo.BoardHillValue,
                CurrentBoardInfo.MaintainRatio,
                null);
        }

        public void SetBoardBackground(string targetFile, double definedBoardHeight, double definedBoardWidth, double hillHeight, bool maintainRatio)
        {
            SetBoardBackground(targetFile, definedBoardHeight, definedBoardWidth, hillHeight, maintainRatio, CurrentBoardInfo.NoisePermutations);
        }
    }
}
